home *** CD-ROM | disk | FTP | other *** search
- /* linuxplay.c - play a sound file on the speaker
- **
- ** Copyright (C) 1995 by Markus Gutschke (gutschk@math.uni-muenster.de)
- **
- ** Parts of this code were inspired by sunplay.c, which is copyright 1989 by
- ** Jef Poskanzer and 1991,92 by Jamie Zawinski; c.f. sunplay.c for further
- ** information.
- **
- ** Permission to use, copy, modify, and distribute this software and its
- ** documentation for any purpose and without fee is hereby granted, provided
- ** that the above copyright notice appear in all copies and that both that
- ** copyright notice and this permission notice appear in supporting
- ** documentation. This software is provided "as is" without express or
- ** implied warranty.
- **
- ** Currently, only SunAudio, Wave, and RAW file formats are actively
- ** supported; VOC file format is detected (and rejected :-( )
- */
-
- #define HEADERSZ 48 /* has to be at least as big as the biggest header */
- #define SNDBUFSZ 2048 /* has to be at least as big as HEADERSZ */
-
- /* XEmacs beta testers say: undef this by default. */
- #undef NOVOLUMECTRLFORMULAW /* Changing the volume for uLaw-encoded
- samples sounds very poor; possibly,
- this is true only for the PC-Snd
- driver, so undefine this symbol at your
- discretion */
-
- #ifdef HAVE_CONFIG_H
- #include <config.h>
- #endif
-
- #include <errno.h>
- #include <fcntl.h>
- #include <linux/soundcard.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/fcntl.h>
- #include <sys/file.h>
- #include <sys/ioctl.h>
- #include <sys/signal.h>
- #include <unistd.h>
-
- #ifdef LINUXPLAYSTANDALONE
- #define perror(str) fprintf(stderr,"audio: %s %s\n",str,strerror(errno));
- #define warn(str) fprintf(stderr,"audio: %s\n",str);
- #else
- #include "lisp.h"
- #define perror(str) message("audio: %s, %s ",str,strerror(errno))
- #define warn(str) message("audio: %s ",GETTEXT(str))
- #endif
-
- #ifdef __GNUC__
- #define UNUSED(x) ((void)(x))
- #else
- #define UNUSED(x)
- #define __inline__
- #endif
-
- static __sighandler_t sighup_handler;
- static __sighandler_t sigint_handler;
-
- static union {
- struct {
- int align;
- enum {wvMain,wvSubchunk,wvOutOfBlock,wvSkipChunk,
- wvSoundChunk,wvFatal,wvFatalNotify} state;
- size_t left;
- unsigned char leftover[HEADERSZ];
- unsigned long chunklength;
- } wave;
- } parsestate;
-
- static unsigned char sndbuf[SNDBUFSZ];
- static int mix_fd = -1;
- static int audio_vol = -1;
- static int audio_fd = -1;
- static char *audio_dev = "";
-
- typedef enum {fmtIllegal,fmtRaw,fmtVoc,fmtWave,fmtSunAudio} fmtType;
-
- static void sighandler(int sig)
- {
- if (audio_fd > 0) {
- ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
- close(audio_fd);
- audio_fd = -1; }
- if (mix_fd > 0) {
- if (audio_vol >= 0) {
- ioctl(mix_fd,SOUND_MIXER_WRITE_VOLUME,&audio_vol);
- audio_vol = -1; }
- close(mix_fd);
- mix_fd = -1; }
- if (sig == SIGHUP && sighup_handler) sighup_handler(sig);
- else if (sig == SIGINT && sigint_handler) sigint_handler(sig);
- else exit(1);
- }
-
- static size_t parseraw(void **data,size_t *sz,void **outbuf)
- {
- int rc = *sz;
-
- *outbuf = *data;
- *sz = 0;
- return(rc);
- }
-
- static size_t parsevoc(void **data,size_t *sz,void **outbuf)
- {
- UNUSED(data);
- UNUSED(sz);
- UNUSED(outbuf);
- return(0);
- }
-
- static __inline__ int waverequire(void **data,size_t *sz,size_t rq)
- {
- int rc = 1;
-
- if (rq > HEADERSZ) {
- warn("Header size exceeded while parsing WAVE file");
- parsestate.wave.state = wvFatal;
- *sz = 0;
- return(0); }
- if ((rq -= parsestate.wave.left) <= 0)
- return(rc);
- if (rq > *sz) {rq = *sz; rc = 0;}
- memcpy(parsestate.wave.leftover+parsestate.wave.left,
- *data,rq);
- parsestate.wave.left += rq;
- ((unsigned char *)*data) += rq;
- *sz -= rq;
- return(rc);
- }
-
- static __inline__ void waveremove(size_t rq)
- {
- if (parsestate.wave.left <= rq)
- parsestate.wave.left = 0;
- else {
- parsestate.wave.left -= rq;
- memmove(parsestate.wave.leftover,
- parsestate.wave.leftover+rq,
- parsestate.wave.left); }
- return;
- }
-
- static size_t parsewave(void **data,size_t *sz,void **outbuf)
- {
- for (;;)
- switch (parsestate.wave.state) {
- case wvMain:
- if (!waverequire(data,sz,20))
- return(0);
- /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec */
- parsestate.wave.chunklength = parsestate.wave.leftover[16] +
- 256*(parsestate.wave.leftover[17] +
- 256*(parsestate.wave.leftover[18] +
- 256*parsestate.wave.leftover[19]));
- waveremove(20);
- parsestate.wave.state = wvSubchunk;
- break;
- case wvSubchunk:
- if (!waverequire(data,sz,parsestate.wave.chunklength))
- return(0);
- parsestate.wave.align = parsestate.wave.chunklength < 14 ? 1
- : parsestate.wave.leftover[2] * parsestate.wave.leftover[12];
- if (parsestate.wave.align != 1 &&
- parsestate.wave.align != 2 &&
- parsestate.wave.align != 4) {
- warn("Illegal datawidth detected while parsing WAVE file");
- parsestate.wave.state = wvFatal; }
- else
- parsestate.wave.state = wvOutOfBlock;
- waveremove(parsestate.wave.chunklength);
- break;
- case wvOutOfBlock:
- if (!waverequire(data,sz,8))
- return(0);
- /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec */
- parsestate.wave.chunklength = parsestate.wave.leftover[4] +
- 256*(parsestate.wave.leftover[5] +
- 256*(parsestate.wave.leftover[6] +
- 256*parsestate.wave.leftover[7]));
- if (memcmp(parsestate.wave.leftover,"data",4))
- parsestate.wave.state = wvSkipChunk;
- else
- if (parsestate.wave.chunklength % parsestate.wave.align) {
- warn("Datawidth does not match chunksize in WAVE file");
- parsestate.wave.state = wvFatal; }
- else
- parsestate.wave.state = wvSoundChunk;
- waveremove(8);
- break;
- case wvSkipChunk:
- if (*sz < parsestate.wave.chunklength) {
- parsestate.wave.chunklength -= *sz;
- *sz = 0; }
- else {
- *sz -= parsestate.wave.chunklength;
- ((unsigned char *)*data) += parsestate.wave.chunklength;
- parsestate.wave.state = wvOutOfBlock; }
- break;
- case wvSoundChunk: {
- size_t count,rq;
- if (parsestate.wave.left) { /* handle leftover bytes from last
- alignment operation */
- count = parsestate.wave.left;
- rq = HEADERSZ-count;
- if (rq > parsestate.wave.chunklength)
- rq = parsestate.wave.chunklength;
- if (!waverequire(data,sz,rq)) {
- parsestate.wave.chunklength -= parsestate.wave.left - count;
- return(0); }
- parsestate.wave.chunklength -= rq;
- *outbuf = parsestate.wave.leftover;
- parsestate.wave.left = 0;
- return(rq); }
- if (*sz >= parsestate.wave.chunklength) {
- count = parsestate.wave.chunklength;
- rq = 0; }
- else {
- count = *sz;
- count -= rq = count % parsestate.wave.align; }
- *outbuf = *data;
- ((unsigned char *)*data) += count;
- *sz -= count;
- if ((parsestate.wave.chunklength -= count) <= 0)
- parsestate.wave.state = wvOutOfBlock;
- else if (rq)
- /* align data length to a multiple of datasize; keep additional data
- in "leftover" buffer --- this is neccessary to ensure proper
- functioning of the sndcnv... routines */
- waverequire(data,sz,rq);
- return(count); }
- case wvFatalNotify:
- warn("Irrecoverable error while parsing WAVE file");
- parsestate.wave.state = wvFatal;
- break;
- case wvFatal:
- default:
- *sz = 0;
- return(0); }
- }
-
- static size_t sndcnvnop(void **data,size_t *sz,void **outbuf)
- {
- int rc = *sz;
-
- *outbuf = *data;
- *sz = 0;
- return(rc);
- }
-
- static size_t sndcnv2mono(void **data,size_t *sz,void **outbuf)
- {
- register unsigned char *src;
- register unsigned char *dest;
- int rc,count;
-
- count = *sz / 2;
- if (count > SNDBUFSZ) {
- *sz -= 2*SNDBUFSZ;
- count = SNDBUFSZ; }
- else
- *sz = 0;
- rc = count;
- src = *data;
- *outbuf =
- dest = sndbuf;
- while (count--)
- *dest++ = (unsigned char)(((int)*((unsigned char *)src)++ +
- (int)*((unsigned char *)src)++) / 2);
- *data = src;
- return(rc);
- }
-
- static size_t sndcnv2byte(void **data,size_t *sz,void **outbuf)
- {
- register unsigned char *src;
- register unsigned char *dest;
- int rc,count;
-
- count = *sz / 2;
- if (count > SNDBUFSZ) {
- *sz -= 2*SNDBUFSZ;
- count = SNDBUFSZ; }
- else
- *sz = 0;
- rc = count;
- src = *data;
- *outbuf =
- dest = sndbuf;
- while (count--) {
- *dest++ = (unsigned char)(((signed char *)src)[1] + 0x80);
- ((char *)src) += 2; }
- *data = src;
- return(rc);
- }
-
- static size_t sndcnv2monobyte(void **data,size_t *sz,void **outbuf)
- {
- register unsigned char *src;
- register unsigned char *dest;
- int rc,count;
-
- count = *sz / 4;
- if (count > SNDBUFSZ) {
- *sz -= 4*SNDBUFSZ;
- count = SNDBUFSZ; }
- else
- *sz = 0;
- rc = count;
- src = *data;
- *outbuf =
- dest = sndbuf;
- while (count--) {
- *dest++ = (unsigned char)(((int)((signed char *)src)[1] +
- (int)((signed char *)src)[3]) / 2 + 0x80);
- ((char *)src) += 4; }
- *data = src;
- return(rc);
- }
-
- static fmtType analyze_format(unsigned char *format,int *fmt,int *speed,
- int *tracks,
- size_t (**parsesndfile)(void **,size_t *sz,
- void **))
- {
- /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec */
- if (!memcmp(format,"Creative Voice File\0x1A\0x1A\x00",22) &&
- (format[22]+256*format[23]+0x1233)&0xFFFF ==
- (format[24]+256*format[25])) { /* VOC */
- *fmt = AFMT_U8;
- *speed = 8000;
- *tracks = 2;
- *parsesndfile = parsevoc;
- return(fmtVoc); }
- else if (!memcmp(format,"RIFF",4) &&
- !memcmp(format+8,"WAVEfmt ",8)) { /* WAVE */
- if (memcmp(format+20,"\001\000\001"/* PCM mono */,4) &&
- memcmp(format+20,"\001\000\002"/* PCM stereo */,4))
- return(fmtIllegal);
- *fmt = format[32] == 1 ? AFMT_U8 : AFMT_S16_LE;
- *tracks = format[22];
- /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec */
- *speed = format[24]+256*(format[25]+256*
- (format[26]+256*format[27]));
- *parsesndfile = parsewave;
- return(fmtWave); }
- else if (!memcmp(format,".snd",4)) { /* Sun Audio */
- *fmt = AFMT_MU_LAW;
- *speed = 8000;
- *tracks = 1;
- *parsesndfile = parseraw;
- return(fmtSunAudio); }
- else {
- *fmt = AFMT_U8;
- *speed = 8000;
- *tracks = 1;
- *parsesndfile = parseraw;
- return(fmtRaw); }
- }
-
- static int audio_init(int mix_fd,int audio_fd,int fmt,int speed,
- int tracks,int *volume,
- size_t (**sndcnv)(void **,size_t *sz,void **))
- {
- int i;
-
- *sndcnv = sndcnvnop;
-
- if (ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL) < 0) {
- perror("SNDCTL_DSP_SYNC");
- return(0); }
-
- if (((i=fmt),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
- fmt != i) {
- if (fmt == AFMT_S16_LE) {
- *sndcnv = sndcnv2mono;
- if (((i=fmt=AFMT_U8),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
- fmt != i) {
- perror("SNDCTL_DSP_SETFMT");
- return(0); } }
- else {
- perror("SNDCTL_DSP_SETFMT");
- return(0); } }
-
- if (((i=tracks-1),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0 ||
- tracks-1 != i) {
- if (tracks == 2) {
- *sndcnv = *sndcnv == sndcnv2mono ? sndcnv2monobyte : sndcnv2byte;
- if (((i = 0),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0 || i) {
- perror("SNDCTL_DSP_STEREO");
- return(0); } }
- else {
- perror("SNDCTL_DSP_STEREO");
- return(0); } }
-
- if (((i=speed),ioctl(audio_fd,SNDCTL_DSP_SPEED,&i)) < 0 ||
- speed*11 < i*10 || speed*9 > i*10) {
- char buffer[256];
- sprintf(buffer,"SNDCTL_DSP_SPEED (req: %d, rtn: %d)",speed,i);
- perror(buffer);
- return(0); }
-
- if (mix_fd > 0) {
- int vol = *volume & 0xFF;
- if (ioctl(mix_fd,SOUND_MIXER_READ_VOLUME,volume) < 0)
- *volume = -1;
- if (vol < 0) vol = 0; else if (vol > 100) vol = 100;
- #ifdef NOVOLUMECTRLFORMULAW
- if (fmt == AFMT_MU_LAW)
- vol = 100;
- #endif
- /* vol = (127*vol)/100; */ /* not using full volume sounds a lot better */
- vol |= 256*vol;
- /* Do not signal an error, if volume control is unavailable! */
- ioctl(mix_fd,SOUND_MIXER_WRITE_VOLUME,&vol); }
- return(1);
- }
-
- static void linux_play_data_or_file(int fd,unsigned char *data,
- int length,int volume)
- {
- size_t (*parsesndfile)(void **data,size_t *sz,void **outbuf);
- size_t (*sndcnv)(void **data,size_t *sz,void **);
- fmtType ffmt;
- int fmt,speed,tracks;
- unsigned char *pptr,*optr,*cptr,*sptr;
- int wrtn,rrtn,crtn,prtn;
-
- if (!data || length < HEADERSZ)
- if (fd < 0) return;
- else {
- length = read(fd,sndbuf,SNDBUFSZ);
- if (length < HEADERSZ)
- return;
- data = sndbuf;
- length = SNDBUFSZ; }
-
- ffmt = analyze_format(data,&fmt,&speed,&tracks,&parsesndfile);
-
- if (ffmt != fmtRaw && ffmt != fmtSunAudio && ffmt != fmtWave) {
- warn("Unsupported file format (neither RAW, nor SunAudio, nor WAVE)");
- return; }
-
- mix_fd = open("/dev/mixer",(O_WRONLY|O_NDELAY),0);
-
- if ((audio_fd = open((audio_dev = ffmt==fmtSunAudio?"/dev/audio":"/dev/dsp"),
- (O_WRONLY|O_NDELAY),0)) < 0) {
- perror(audio_dev);
- if (mix_fd > 0) { close(mix_fd); mix_fd = -1; }
- return; }
-
- sighup_handler = signal(SIGHUP,(__sighandler_t)sighandler);
- sigint_handler = signal(SIGINT,(__sighandler_t)sighandler);
-
- if (!audio_init(mix_fd,audio_fd,fmt,speed,tracks,&volume,&sndcnv))
- goto END_OF_PLAY;
- audio_vol = volume;
-
- memset(&parsestate,0,sizeof(parsestate));
-
- rrtn = length;
- do {
- for (pptr = data; (prtn = parsesndfile((void **)&pptr,&rrtn,
- (void **)&optr)) > 0; )
- for (cptr = optr; (crtn = sndcnv((void **)&cptr,&prtn,
- (void **)&sptr)) > 0; ) {
- for (;;) {
- if ((wrtn = write(audio_fd,sptr,crtn)) < 0) {
- perror("write"); goto END_OF_PLAY; }
- else if (wrtn) break;
- else if (ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL) < 0) {
- perror("SNDCTL_DSP_SYNC"); goto END_OF_PLAY; } }
- if (wrtn != crtn) {
- char buf[255];
- sprintf(buf,"play: crtn = %d, wrtn = %d",crtn,wrtn);
- warn(buf);
- goto END_OF_PLAY; } }
- if (fd >= 0) {
- if ((rrtn = read(fd,sndbuf,SNDBUFSZ)) < 0) {
- perror("read"); goto END_OF_PLAY; } }
- else
- break;
- } while (rrtn > 0);
-
- if (ffmt == fmtWave && parsestate.wave.state != wvOutOfBlock &&
- parsestate.wave.state != wvFatal)
- warn("Unexpected end of WAVE file");
-
- ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL);
- END_OF_PLAY:
- ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
-
- signal(SIGHUP,sighup_handler);
- signal(SIGINT,sigint_handler);
-
- close(audio_fd);
- audio_fd = -1;
-
- if (mix_fd > 0) {
- if (audio_vol >= 0) {
- ioctl(mix_fd,SOUND_MIXER_WRITE_VOLUME,&audio_vol);
- audio_vol = -1; }
- close(mix_fd); mix_fd = -1; }
-
- return;
- }
-
- void play_sound_file (char *sound_file, int volume)
- {
- int fd;
-
- if ((fd=open(sound_file,O_RDONLY,0)) < 0) {
- perror(sound_file);
- return; }
- linux_play_data_or_file(fd,NULL,0,volume);
- close(fd);
- return;
- }
-
- void play_sound_data (unsigned char *data, int length, int volume)
- {
- linux_play_data_or_file(-1,data,length,volume);
- return;
- }
-